﻿namespace DeltaGroup.WheelOfJeopardy.UI.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Animation;

    using DeltaGroup.WheelOfJeopardy.Model;
    using DeltaGroup.WheelOfJeopardy.UI.Events;
    using DeltaGroup.WheelOfJeopardy.UI.ViewModel;
    using DeltaGroup.WheelOfJeopardy.Util;

    /// <summary>
    /// Interaction logic for Wheel.xaml
    /// </summary>
    public sealed partial class Wheel : UserControl
    {
        #region Fields

        /// <summary>
        /// The dependency property that gets or sets the RotateTransformAngle of the Wheel.
        /// </summary>
        public static DependencyProperty RotateTransformAngleProperty = DependencyProperty.Register("RotateTransformAngle", typeof(double), typeof(Wheel));

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Wheel"/> class.
        /// </summary>
        public Wheel()
        {
            InitializeComponent();
        }

        #endregion Constructors

        #region Delegates

        /// <summary>
        /// Delegate for event handler
        /// </summary>
        public delegate void SpinCompleteEventHandler(object sender, SectorSelectedEventArgs e);

        #endregion Delegates

        #region Events

        /// <summary>
        /// Occurs when [spin completed].
        /// </summary>
        public event SpinCompleteEventHandler SpinCompleted;

        #endregion Events

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether [debug mode].
        /// </summary>
        /// <value><c>true</c> if [debug mode]; otherwise, <c>false</c>.</value>
        public bool DebugMode
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the rotate transform angle.
        /// </summary>
        /// <value>The rotate transform angle.</value>
        public double RotateTransformAngle
        {
            get { return (double)GetValue(RotateTransformAngleProperty); }
            set { SetValue(RotateTransformAngleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the selected sector.
        /// </summary>
        /// <value>The selected sector.</value>
        public SectorInfo SelectedSector
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the rotation value.
        /// </summary>
        /// <value>The rotation value.</value>
        private double RotationValue
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the wheel sector count.
        /// </summary>
        /// <value>The wheel sector count.</value>
        private int WheelSectorCount
        {
            get
            {
                return WheelSectors.Children.Count;
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Layouts the sectors.
        /// </summary>
        /// <param name="sectors">The sectors.</param>
        public void LayoutSectors(List<SectorInfo> sectorInfos)
        {
            int sectorInfoCount = sectorInfos.Count;

            ClearWheelSectors();

            for (int index = 0; index < sectorInfoCount; index++)
            {
                WheelSector wheelSector = CreateWheelSector(sectorInfos[index], index, sectorInfoCount);
                wheelSector.WheelSectorClicked += SpinToSelectedSector;
                WheelSectors.Children.Add(wheelSector);
            }
        }

        /// <summary>
        /// Spins the wheel.
        /// </summary>
        /// <param name="powerLevel">The power level.</param>
        public void SpinWheel(double degrees)
        {
            RotationValue = degrees;

            Storyboard spinWheelStoryboard = GetSpinWheelStoryboard();
            double currentAngle = GetCurrentRotationValue(spinWheelStoryboard);
            UpdateRotationValue(spinWheelStoryboard, currentAngle + RotationValue);
            spinWheelStoryboard.Begin();
        }

        /// <summary>
        /// Prepares for spin.
        /// </summary>
        internal void PrepareForSpin()
        {
            ShowContinueLink(false);
            SelectedSector = null;
        }

        /// <summary>
        /// Clears the wheel sectors.
        /// </summary>
        private void ClearWheelSectors()
        {
            WheelSectors.Children.RemoveRange(0, WheelSectorCount);
        }

        /// <summary>
        /// Continues the selected.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void ContinueSelected(object sender, EventArgs e)
        {
            ShowContinueLink(false);
            RotateTransformAngle = RotationValue;
            RaiseSpinCompletedEvent();
        }

        /// <summary>
        /// Creates the wheel sector.
        /// </summary>
        /// <param name="info">The info.</param>
        /// <param name="position">The position.</param>
        /// <returns></returns>
        private WheelSector CreateWheelSector(SectorInfo info, int position, int sectorCount)
        {
            double sectorAngle = WheelMathUtil.CalculateWheelSectorDegrees(sectorCount);
            info.StartAngle = position * sectorAngle;
            info.EndAngle = info.StartAngle + sectorAngle;
            info.ArcAngle = sectorAngle;
            info.Scale = 1d;
            WheelSector sector = new WheelSector(info);
            sector.Height = WheelMathUtil.CIRCLE_DIAMETER;
            sector.Width = WheelMathUtil.CIRCLE_DIAMETER;

            return sector;
        }

        /// <summary>
        /// Gets the current rotation value.
        /// </summary>
        /// <param name="spinWheelStoryboard">The spin wheel storyboard.</param>
        /// <returns></returns>
        private double GetCurrentRotationValue(Storyboard spinWheelStoryboard)
        {
            DoubleKeyFrame keyFrame = (spinWheelStoryboard.Children[0] as DoubleAnimationUsingKeyFrames).KeyFrames[0] as DoubleKeyFrame;
            if (keyFrame != null)
            {
                return keyFrame.Value;
            }
            else {
                return 0d;
            }
        }

        /// <summary>
        /// Gets the selected sector.
        /// </summary>
        /// <returns></returns>
        private SectorInfo GetSelectedSector()
        {
            int sectorIndex = WheelMathUtil.CalculateSelectedSectorIndex(WheelSectorCount, GetCurrentRotationValue(GetSpinWheelStoryboard()));
            WheelSector selectedSector = WheelSectors.Children[sectorIndex] as WheelSector;
            return selectedSector.Sector;
        }

        /// <summary>
        /// Gets the spin wheel storyboard.
        /// </summary>
        /// <returns></returns>
        private Storyboard GetSpinWheelStoryboard()
        {
            Storyboard storyboard = this.Resources["spinWheelStoryboard"] as Storyboard;
            if (storyboard == null)
            {
                throw new MissingFieldException("Storyboard [spinWheelStoryboard] not found in Wheel resources.");
            }

            return storyboard;
        }

        /// <summary>
        /// Raises the spin completed event.
        /// </summary>
        private void RaiseSpinCompletedEvent()
        {
            if (SpinCompleted != null)
            {
                SectorInfo sectorInfo = GetSelectedSector();
                SpinCompleted(this, new SectorSelectedEventArgs(sectorInfo.Sector));
            }
        }

        /// <summary>
        /// Shows the continue link.
        /// </summary>
        /// <param name="visible">if set to <c>true</c> [visible].</param>
        private void ShowContinueLink(bool visible)
        {
            continueLink.Visibility = (visible ? System.Windows.Visibility.Visible : System.Windows.Visibility.Hidden);
        }

        /// <summary>
        /// Handles the WheelSectorClicked event of the wheelSector control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void SpinToSelectedSector(object sender, EventArgs e)
        {
            if(DebugMode)
                SpinToWheelSector(sender as WheelSector);
        }

        /// <summary>
        /// Spins to wheel sector.
        /// </summary>
        /// <param name="wheelSector">The wheel sector.</param>
        private void SpinToWheelSector(WheelSector wheelSector)
        {
            if (wheelSector != null) {
                int index = WheelSectors.Children.IndexOf(wheelSector);
                double rotationValue = GetCurrentRotationValue(GetSpinWheelStoryboard());
                double sectorAngle = WheelMathUtil.CalculateRotationDegreesToWheelSectorByIndex(WheelSectorCount, index, rotationValue);

                SpinWheel(sectorAngle);
            }
        }

        /// <summary>
        /// Handles the Completed event of the Storyboard control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Storyboard_Completed(object sender, EventArgs e)
        {
            SelectedSector = GetSelectedSector();
            ShowContinueLink(true);
        }

        /// <summary>
        /// Updates the rotation value.
        /// </summary>
        /// <param name="spinWheelStoryboard">The spin wheel storyboard.</param>
        /// <param name="rotationValue">The rotation value.</param>
        private void UpdateRotationValue(Storyboard spinWheelStoryboard, double rotationValue)
        {
            //EasingDoubleKeyFrame does not derive from FrameworkElement (from DependencyObject),
            //so can't bind data to its value through DependencyProperty.
            DoubleKeyFrame keyFrame = (spinWheelStoryboard.Children[0] as DoubleAnimationUsingKeyFrames).KeyFrames[0] as DoubleKeyFrame;
            if (keyFrame != null)
            {
                keyFrame.Value = rotationValue;
            }
        }

        #endregion Methods
    }
}